import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.*;

import memory.Memory;

import commands.*;
import commands.ConditionalCommand.Type;
import exceptions.DivByZeroException;
import exceptions.ParseException;

public class Program {
	Command start;
	Scanner input;
	PrintWriter output;
	Memory memory;

	Program(FileReader fileReader) throws ParseException, IOException {
		memory = new Memory();
		input = new Scanner(System.in);
		output = new PrintWriter(System.out);

		BufferedReader in = new BufferedReader(fileReader);
		String curString;

		curString = in.readLine();

		Command last = null;
		int lineNumber = 0;
		Stack<ConditionalCommand> conditionals = new Stack<ConditionalCommand>();

		while (curString != null) {
			if (curString.indexOf("#") != -1)
				curString = curString.substring(0, curString.indexOf("#"));
			// System.err.println(":)");
			lineNumber++;
			if (!new StringTokenizer(curString).hasMoreTokens()) {
				curString = in.readLine();
				continue;
			}
			String t = new StringTokenizer(curString, " =\n\t").nextToken();
			Command curCommand = null;

			if (t.equals("if") || t.equals("while")) {
				try {
					curCommand = createConditional(curString, last, lineNumber,
							conditionals);
				} catch (ParseException e) {
					throw new ParseException("line " + lineNumber
							+ ": syntax error: " + e.getMessage());

				}
			}

			if (t.equals("end")) {
				try {
					curCommand = createEnd(curString, last, lineNumber,
							conditionals);
				} catch (ParseException e) {
					throw new ParseException("line " + lineNumber
							+ ": syntax error: " + e.getMessage());

				}
			}
			if (t.equals("print")) {
				try {
					curCommand = new PrintCommand(lineNumber, curString,
							output, memory);
				} catch (ParseException e) {
					throw new ParseException("line " + lineNumber
							+ ": syntax error: " + e.getMessage());
				}
				if (last == null) {
					start = curCommand;
				} else {
					last.setNext(curCommand);
				}
			}
			if (t.equals("read")) {
				try {
					curCommand = new ReadCommand(lineNumber, memory, input,
							curString);
				} catch (ParseException e) {
					throw new ParseException("line " + lineNumber
							+ ": syntax error: " + e.getMessage());
				}
				if (last == null) {
					start = curCommand;
				} else {
					last.setNext(curCommand);
				}
			}
			if ((curCommand == null) && (Memory.isCorrectVariableName(t))) {
				try {
					curCommand = new AssignCommand(curString, memory,
							lineNumber);
				} catch (ParseException e) {
					throw new ParseException("line " + lineNumber
							+ ": syntax error: " + e.getMessage());
				}
				if (last == null) {
					start = curCommand;
				} else {
					last.setNext(curCommand);
				}
			}
			if (curCommand == null) {
				throw new ParseException("line " + lineNumber
						+ ": syntax error: unexpected token " + t);
			}
			last = curCommand;
			curString = in.readLine();
		}
		if (!conditionals.isEmpty()) {
			throw new ParseException("line " + lineNumber
					+ ": syntax error: unexpected end of program");
		}
	}

	private Command createEnd(String curString, Command last, int lineNumber,
			Stack<ConditionalCommand> conditionals) throws ParseException {
		Command curCommand;
		curCommand = new EndCommand(lineNumber);
		if (conditionals.isEmpty()) {
			output.println("line " + lineNumber
					+ ": syntax error: unexpected end");
			throw new ParseException("Unexpected end");
		}
		if (new StringTokenizer(curString).countTokens() > 1) {
			output.println("line " + lineNumber
					+ ": syntax error: extra tokens after end");
			throw new ParseException("Extra tokens after end");
		}
		ConditionalCommand lastConditional = conditionals.pop();
		lastConditional.setFalseNext(curCommand);
		if (lastConditional.getTrueNext() == null) {
			if (lastConditional.getType() == Type.IF) {
				lastConditional.setNext(curCommand);
			} else {
				last.setNext(lastConditional);
			}
		} else {
			if (lastConditional.getType() == Type.IF) {
				last.setNext(curCommand);
			} else {
				last.setNext(lastConditional);
			}
		}
		return curCommand;
	}

	private Command createConditional(String curString, Command last,
			int lineNumber, Stack<ConditionalCommand> conditionals)
			throws ParseException {
		Command curCommand;
		conditionals
				.push(new ConditionalCommand(curString, memory, lineNumber));
		curCommand = conditionals.peek();
		if (last == null) {
			start = curCommand;
		} else {
			last.setNext(curCommand);
		}
		return curCommand;
	}

	public void run() throws DivByZeroException {
		Command current = start;
		while (current != null) {
			try {
				current.run();
			} catch (ArithmeticException e) {
				throw new DivByZeroException("line " + current.getLineNumber()
						+ ": division by zero");
			}
			current = current.getNext();	
		}
	}
}
